﻿using System;
using System.Collections.Generic;
using System.Xml;

namespace IniFileManager
{
	/// <summary>
	/// Class representing inner structure of a property 
	/// within section defined by library user in ini schema.
	/// </summary>
	public sealed class IniPropertySchema
	{
		private const string unexpectedNodeError = " unexpected node.";
		private const string elementFormatError = "{0}.[{1}]element.";

		private readonly string name;
		private readonly bool isRequired;
		private readonly List<IniElementSchema> elements;

		/// <summary>
		/// Creates "IniPropertySchema" from xml node in ini schema xml representation.
		/// </summary>
		/// <param name="propertyNode">Ini property schema xml node.</param>
		/// <exception cref="System.FormatException">Trown if is provided xml node not valid property node.</exception>
		internal IniPropertySchema( XmlNode propertyNode )
		{
			// Embedded "IniSchema.xsd"'s definition guarantees existence and format of these attributes:
			this.name = propertyNode.Attributes[IniSchemaXsd.ATTRIBUTE_NAME].Value;
			this.isRequired = bool.Parse( propertyNode.Attributes[IniSchemaXsd.ATTRIBUTE_REQUIRED].Value );
			this.elements = new List<IniElementSchema>();

			// Embedded "IniSchema.xsd"'s definition guarantees that every child of "property" is one of elements:
			XmlNodeList elementNodes = propertyNode.ChildNodes;
			foreach( XmlNode elementNode in elementNodes )
			{
				try
				{
					// In case of semantical exceptions in "elementNode" we can expect 
					// "FormatException" while trying to get "IniElementSchema" from "elementNode".
					elements.Add( GetElement( elementNode ) );
				}
				catch( FormatException )
				{
					throw new FormatException( String.Format( elementFormatError, this.name, this.elements.Count ) );
				}
			}
		}

		/// <summary>
		/// Unique property's name within ini section schema.
		/// </summary>
		internal string Name
		{
			get
			{
				return this.name;
			}
		}
		/// <summary>
		/// Boolean representing wheather is property required within ini section schema.
		/// </summary>
		internal bool IsRequired
		{
			get
			{
				return this.isRequired;
			}
		}
		internal List<IniElementSchema> Elements
		{
			get
			{
				return this.elements;
			}
		}

		/// <summary>
		/// Gets an element schema constructed according to the "elementNode"'s name.
		/// </summary>
		/// <param name="elementNode">Element schema's xml node.</param>
		/// <returns>Constructed appropriate ini element schema.</returns>
		/// <exception cref="System.FormatException">Trown if is provided xml node not valid element node.</exception>
		private IniElementSchema GetElement( XmlNode elementNode )
		{
			switch( elementNode.Name )
			{
				case IniSchemaXsd.ELEMENT_BOOL_ELEMENT:
					return new IniBoolElementSchema( elementNode );
				case IniSchemaXsd.ELEMENT_SIGNED_ELEMENT:
					return new IniSignedElementSchema( elementNode );
				case IniSchemaXsd.ELEMENT_UNSIGNED_ELEMENT:
					return new IniUnsignedElementSchema( elementNode );
				case IniSchemaXsd.ELEMENT_FLOAT_ELEMENT:
					return new IniFloatElementSchema( elementNode );
				case IniSchemaXsd.ELEMENT_STRING_ELEMENT:
					return new IniStringElementSchema( elementNode );
				case IniSchemaXsd.ELEMENT_ENUM_ELEMENT:
					return new IniEnumElementSchema( elementNode );
				default:
					// "IniSchema.xsd" excludes this throw as unreachable code.
					// However this throw is demanded by C# syntax, it is an insurance:
					throw new XmlException( unexpectedNodeError );
			}

		}
	}
}
